﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Serious
{
    public class Terrain
    {
        public Single[,] heightmap;
        public int heightmap_width
        {
            get
            {
                return heightmap.GetLength(0);
            }
        }
        public int heightmap_height
        {
            get
            {
                return heightmap.GetLength(1);
            }
        }
        public Vector3 position;
        public Single scale;


        public VertexPositionColorNormal[] model_vertices;
        public BasicModel model;

        public Terrain(Texture2D heightmap_bmp, Vector3 position, Single scale)
        {
            this.position = position;
            this.scale = scale;

            initialize_heightmap(heightmap_bmp);
            initialize_model();
        }

        public void initialize_heightmap(Texture2D heightmap_bmp)
        {
            heightmap = new Single[heightmap_bmp.Width, heightmap_bmp.Height];
            Color[] heightmap_bmp_data = new Color[heightmap.Length];
            Color[][] poop = new Color[9][];
            heightmap_bmp.GetData(heightmap_bmp_data);

            for (int y = 0; y < heightmap_height; y++) for (int x = 0; x < heightmap_width; x++)
            {
                Single h = 0.0f;
                int w = 0;
                if (w == 0)
                {
                    h = heightmap_bmp_data[flat_index(new Index2(x, y))].R / 15.0f;
                }
                else if (w == 1)
                {
                    Single m = Vector2.Distance(new Vector2(x, y), new Vector2(48.0f) / 2.0f) / (32.0f / 2.0f);
                    m = Math.Min(m, 1.0f);
                    Single n = ((Single)Math.Cos(m * Math.PI) + 1.0f) / 2.0f;
                    n *= 10.0f;
                    h = n;
                }
                else if (w == 2)
                {
                    Single a = 0.0f;
                    Single b = 20.0f;
                    Single c = 0.0f;
                    Single d = 0.0f;
                    Single xx = (Single)x / ((Single)heightmap_width - 1.0f);
                    Single yy = (Single)y / ((Single)heightmap_height - 1.0f);
                    h = MathHelper.Lerp(MathHelper.Lerp(a, b, xx), MathHelper.Lerp(c, d, xx), yy);
                }
                else if (w == 3)
                {
                    Single a = 0.0f;
                    Single b = 20.0f;
                    Single c = 20.0f;
                    Single d = 40.0f;
                    Single xx = (Single)x / ((Single)heightmap_width - 1.0f);
                    Single yy = (Single)y / ((Single)heightmap_height - 1.0f);
                    h = MathHelper.SmoothStep(MathHelper.SmoothStep(a, b, xx), MathHelper.SmoothStep(c, d, xx), yy);
                }
                else if (w == 4)
                {
                    Single a = 0.0f;
                    Single b = 30.0f;
                    Single c = 30.0f;
                    Single d = 0.0f;
                    Single xx = (Single)x / ((Single)heightmap_width - 1.0f);
                    Single yy = (Single)y / ((Single)heightmap_height - 1.0f);
                    h = MathHelper.SmoothStep(MathHelper.SmoothStep(a, b, xx), MathHelper.SmoothStep(c, d, xx), yy);
                }
                heightmap[x, y] = h;
            }
        }

        public void initialize_model()
        {
            Func<bool, Index2, int> flat_index = delegate(bool is_centerized_thingy, Index2 heightmap_index)
            {
                if (!is_centerized_thingy)
                {
                    return this.flat_index(heightmap_index);
                }
                else
                {
                    return heightmap.Length + heightmap_index.X + (heightmap_width - 1) * heightmap_index.Y;
                }
            };
            VertexPositionColorNormal[] vertices = new VertexPositionColorNormal[heightmap.Length + (heightmap_width - 1) * (heightmap_height - 1)];

            for (int y = 0; y < heightmap_height; y++) for (int x = 0; x < heightmap_width; x++)
            {
                Index2 i = new Index2(x, y);
                Color color = Color.White;
                Single H = 10.0f;
                Single h = heightmap[x, y];
                Color c_high = h > 0.0f ? Color.Green : new Color(32, 0, 32);
                h = Math.Abs(h);
                if (h < H)
                {
                    color = Color.Lerp(new Color(128, 64, 32), c_high, h / H);
                }
                else
                {
                    color = c_high;
                }
                vertices[flat_index(false, i)].Position = new Vector3(x, y, heightmap[x, y]);
                vertices[flat_index(false, i)].Color = color;

                if (x < heightmap_width - 1 && y < heightmap_height - 1)
                {
                    Vector2 v = i + new Vector2(0.5f);
                    vertices[flat_index(true, i)].Position = new Vector3(v, height_at(v).Value);
                }
            }

            for (int y = 0; y < heightmap_height - 1; y++) for (int x = 0; x < heightmap_width - 1; x++)
            {
                Index2 i00 = new Index2(x, y);
                Index2 i10 = i00 + new Index2(1, 0);
                Index2 i01 = i00 + new Index2(0, 1);
                Index2 i11 = i00 + new Index2(1, 1);

                int f00 = flat_index(false, i00);
                int f10 = flat_index(false, i10);
                int f01 = flat_index(false, i01);
                int f11 = flat_index(false, i11);

                Vector3 v00 = heightmap_index_2_world_vertex(i00);
                Vector3 v10 = heightmap_index_2_world_vertex(i10);
                Vector3 v01 = heightmap_index_2_world_vertex(i01);
                Vector3 v11 = heightmap_index_2_world_vertex(i11);

                Vector3 v1, v2;

                v1 = v10 - v00;
                v2 = v01 - v00;
                vertices[f00].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));

                v1 = v11 - v10;
                v2 = v00 - v10;
                vertices[f10].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));

                v1 = v01 - v11;
                v2 = v10 - v11;
                vertices[f11].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));

                v1 = v00 - v01;
                v2 = v11 - v01;
                vertices[f01].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));
            }
            // dont think this is necessary, because XNA is nice about it or whatever
            /*
            for (int k=0; k<vertices.Length; k++) vertices[k].Normal.Normalize();
            */

            // assume heightmap_width >= heightmap_height

            int[] indeces = new int[12 * (heightmap_width - 1) * (heightmap_height - 1)];
            int index = 0;
            for (int y = 0; y < heightmap_height - 1; y++) for (int x = 0; x < heightmap_width - 1; x++)
            {
                Index2 i00 = new Index2(x, y);
                Index2 i10 = i00 + new Index2(1, 0);
                Index2 i01 = i00 + new Index2(0, 1);
                Index2 i11 = i00 + new Index2(1, 1);

                int fi00 = flat_index(false, i00);
                int fi10 = flat_index(false, i10);
                int fi01 = flat_index(false, i01);
                int fi11 = flat_index(false, i11);
                int fi5 = flat_index(true, i00);

                indeces[index++] = fi5;
                indeces[index++] = fi00;
                indeces[index++] = fi01;

                indeces[index++] = fi5;
                indeces[index++] = fi01;
                indeces[index++] = fi11;

                indeces[index++] = fi5;
                indeces[index++] = fi11;
                indeces[index++] = fi10;

                indeces[index++] = fi5;
                indeces[index++] = fi10;
                indeces[index++] = fi00;

                // also this:
                vertices[fi5].Normal = vertices[fi00].Normal + vertices[fi10].Normal + vertices[fi01].Normal + vertices[fi11].Normal;
                vertices[fi5].Color = new Color((vertices[fi00].Color.ToVector3() + vertices[fi10].Color.ToVector3() + vertices[fi01].Color.ToVector3() + vertices[fi11].Color.ToVector3()) / 4.0f);
            }


            BasicModel.ActualRenderer renderer = new BasicModel.IndecesRenderer<VertexPositionColorNormal>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indeces, 0, indeces.Length / 3);
            model_vertices = vertices;
            model = new BasicModel(new BasicModel.ActualRenderer[] { renderer });
        }

        public void set_model(Index2 origin, Index2 size, Func<Vector3, Single> f)
        {
            int low_x = Math.Max(origin.X, 0);
            int low_y = Math.Max(origin.Y, 0);
            int hi_x = Math.Min(heightmap_width, origin.X + size.X);
            int hi_y = Math.Min(heightmap_height, origin.Y + size.Y);
            for (int x = low_x; x < hi_x; x++) for (int y = low_y; y < hi_y; y++)
            {
                heightmap[x, y] = (f(heightmap_index_2_world_vertex(new Index2(x, y))) - position.Z) / scale;
            }
            int e = 2;
            low_x = Math.Max(origin.X - e, 0);
            low_y = Math.Max(origin.Y - e, 0);
            hi_x = Math.Min(heightmap_width, origin.X + size.X + e);
            hi_y = Math.Min(heightmap_height, origin.Y + size.Y + e);

            Func<bool, Index2, int> flat_index = delegate(bool is_centerized_thingy, Index2 heightmap_index)
            {
                if (!is_centerized_thingy)
                {
                    return this.flat_index(heightmap_index);
                }
                else
                {
                    return heightmap.Length + heightmap_index.X + (heightmap_width - 1) * heightmap_index.Y;
                }
            };

            for (int y = low_y; y < hi_y; y++) for (int x = low_x; x < hi_x; x++)
            {
                Index2 i = new Index2(x, y);
                Color color = Color.White;
                Single H = 10.0f;
                Single h = heightmap[x, y];
                Color c_high = h > 0.0f ? Color.Green : new Color(32, 0, 32);
                h = Math.Abs(h);
                if (h < H)
                {
                    color = Color.Lerp(new Color(128, 64, 32), c_high, h / H);
                }
                else
                {
                    color = c_high;
                }
                model_vertices[flat_index(false, i)].Position.Z = heightmap[x, y];
                model_vertices[flat_index(false, i)].Color = color;
                model_vertices[flat_index(false, i)].Normal = Vector3.Zero;

                if (x < heightmap_width - 1 && y < heightmap_height - 1)
                {
                    Vector2 v = i + new Vector2(0.5f);
                    model_vertices[flat_index(true, i)].Position = new Vector3(v, height_at(v).Value);
                }
            }

            for (int y = low_y - 1; y < hi_y; y++) for (int x = low_x - 1; x < hi_x; x++)
            {
                if (!(x >= 0 && x < heightmap_width-1 && y >= 0 && y < heightmap_height-1)) continue;
                
                Index2 i00 = new Index2(x, y);
                Index2 i10 = i00 + new Index2(1, 0);
                Index2 i01 = i00 + new Index2(0, 1);
                Index2 i11 = i00 + new Index2(1, 1);

                int f00 = flat_index(false, i00);
                int f10 = flat_index(false, i10);
                int f01 = flat_index(false, i01);
                int f11 = flat_index(false, i11);

                Vector3 v00 = heightmap_index_2_world_vertex(i00);
                Vector3 v10 = heightmap_index_2_world_vertex(i10);
                Vector3 v01 = heightmap_index_2_world_vertex(i01);
                Vector3 v11 = heightmap_index_2_world_vertex(i11);

                Vector3 v1, v2;

                if (i00.X >= low_x && i00.X < hi_x && i00.Y >= low_y && i00.Y < hi_y)
                {
                    v1 = v10 - v00;
                    v2 = v01 - v00;
                    model_vertices[f00].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));
                }

                if (i10.X >= low_x && i10.X < hi_x && i10.Y >= low_y && i10.Y < hi_y)
                {
                    v1 = v11 - v10;
                    v2 = v00 - v10;
                    model_vertices[f10].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));
                }

                if (i11.X >= low_x && i11.X < hi_x && i11.Y >= low_y && i11.Y < hi_y)
                {
                    v1 = v01 - v11;
                    v2 = v10 - v11;
                    model_vertices[f11].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));
                }

                if (i01.X >= low_x && i01.X < hi_x && i01.Y >= low_y && i01.Y < hi_y)
                {
                    v1 = v00 - v01;
                    v2 = v11 - v01;
                    model_vertices[f01].Normal += Vector3.Normalize(Vector3.Cross(v1, v2));
                }
            }

            for (int y = low_y; y < hi_y-1; y++) for (int x = low_x; x < hi_x-1; x++)
            {
                Index2 i00 = new Index2(x, y);
                Index2 i10 = i00 + new Index2(1, 0);
                Index2 i01 = i00 + new Index2(0, 1);
                Index2 i11 = i00 + new Index2(1, 1);

                int fi00 = flat_index(false, i00);
                int fi10 = flat_index(false, i10);
                int fi01 = flat_index(false, i01);
                int fi11 = flat_index(false, i11);
                int fi5 = flat_index(true, i00);

                model_vertices[fi5].Normal = model_vertices[fi00].Normal + model_vertices[fi10].Normal + model_vertices[fi01].Normal + model_vertices[fi11].Normal;
                model_vertices[fi5].Color = new Color((model_vertices[fi00].Color.ToVector3() + model_vertices[fi10].Color.ToVector3() + model_vertices[fi01].Color.ToVector3() + model_vertices[fi11].Color.ToVector3()) / 4.0f);
            }
        }

        private int flat_index(Index2 i)
        {
            return i.X + heightmap_width * i.Y;
        }

        private Vector3 heightmap_index_2_world_vertex(Index2 i)
        {
            return position + scale * new Vector3(i.X, i.Y, heightmap[i.X, i.Y]);
        }

        private Single? height_at(Vector2 normalized_world_coord)
        {
            Index2 wi = Index2.CastFromVector2(normalized_world_coord);

            if (normalized_world_coord.X < 0 || normalized_world_coord.X > heightmap_width - 1 ||
                normalized_world_coord.Y < 0 || normalized_world_coord.Y > heightmap_height - 1)
            {
                return null;
            }

            int mx = wi.X + 1;
            int my = wi.Y + 1;
            if (normalized_world_coord.X == heightmap_width - 1)
            {
                mx = wi.X;
            }
            if (normalized_world_coord.Y == heightmap_height - 1)
            {
                my = wi.Y;
            }

            Single a = heightmap[wi.X, wi.Y];
            Single b = heightmap[mx, wi.Y];
            Single c = heightmap[wi.X, my];
            Single d = heightmap[mx, my];

            Single x = normalized_world_coord.X - wi.X;
            Single y = normalized_world_coord.Y - wi.Y;
            Single h = MathHelper.Lerp(MathHelper.Lerp(a, b, x), MathHelper.Lerp(c, d, x), y);

            return h;
        }





        public Single? z_at(Vector2 world_coord)
        {
            Single? height = height_at((world_coord - new Vector2(position.X, position.Y)) / scale);
            if (height == null) return null;

            return position.Z + scale * height.Value;
        }

        public void Draw(GraphicsDevice device)
        {
            model.Draw(device);
        }
    }
}
